<script>
import Loading from '@/components/Loading';
import { mapGetters, mapState } from 'vuex';
import { NAME as MANAGER } from '@/config/product/manager';
import { CAPI, MANAGEMENT } from '@/config/types';
import { SETTING } from '@/config/settings';

const EMBER_FRAME = 'ember-iframe';
const EMBER_FRAME_HIDE_CLASS = 'ember-iframe-hidden';
const PAGE_CHECK_TIMEOUT = 30000;
const WINDOW_MANAGER = 'windowmanager';

// Remove the IFrame if the user has not used an embedded page after this time
// since last visiting an embedded page
const INACTIVITY_CHECK_TIMEOUT = 60000;

let inactiveRemoveTimer = null;

// Pages that we should intercept when loaded in the IFRAME and instead
// navigate to a page in Cluster Dashboard
// exmample if the Ember clusters page that is navigated to when the user presses cancel on some pages
// we intercept this and go the the vue Clusters page instead
const INTERCEPTS = {
  'global-admin.clusters.index': {
    name:   'c-cluster-product-resource',
    params: {
      cluster:  '',
      product:  MANAGER,
      resource: CAPI.RANCHER_CLUSTER,
    }
  },
  'authenticated.cluster.index': {
    name:   'c-cluster-product-resource',
    params: {
      cluster:  '',
      product:  MANAGER,
      resource: CAPI.RANCHER_CLUSTER,
    }
  },
  'global-admin.catalog': {
    name:   'c-cluster-mcapps-pages-page',
    params: {
      cluster: 'local',
      product: 'mcapps',
      page:    'catalogs'
    }
  },
  'authenticated.cluster.istio.cluster-setting': { name: 'c-cluster-explorer-tools' },
};

export default {
  components: { Loading },

  props: {
    src: {
      type:     String,
      required: true
    },
    pop: {
      type:    Boolean,
      default: false
    },
    inline: {
      type:    String,
      default: ''
    },
    forceReuse: {
      type:    Boolean,
      default: false
    }
  },

  data() {
    return {
      iframeEl:         null,
      loaded:           true,
      loadRequired:     false,
      emberCheck:       null,
      error:            false,
      heightSync:       null,
      frameHeight:      -1,
      frameWidth:       -1,
      wmHeight:         -1,
      showHeaderBanner: false,
      showFooterBanner: false,
    };
  },

  computed: {
    ...mapGetters({ theme: 'prefs/theme' }),
    ...mapGetters(['clusterId', 'productId']),
    ...mapState('wm', ['open']),
  },

  watch: {
    theme(theme) {
      this.notifyTheme(theme);
    },

    // Update when source property changes
    src(nue, old) {
      if (nue !== old) {
        this.initFrame();
      }
    },

    // Watch on the window manager opening/closing
    open(nue, old) {
      if (nue !== old) {
        if (nue) {
          this.syncSize();
        } else {
          clearTimeout(this.heightSync);
          const iframeEl = document.getElementById(EMBER_FRAME);

          // Reset the height when the window manager is closed
          this.heightSync = null;
          this.wmHeight = -1;

          if (iframeEl) {
            iframeEl.style.height = '';
          }
        }
      }
    }
  },

  mounted() {
    // Embedded page visited, so cancel time to remove IFRAME when inactive
    clearTimeout(inactiveRemoveTimer);
    window.addEventListener('message', this.receiveMessage);
    this.initFrame();
  },

  beforeDestroy() {
    window.removeEventListener('message', this.receiveMessage);

    if (this.heightSync) {
      clearTimeout(this.heightSync);
    }

    if (this.inline) {
      const iframeEl = document.getElementById(EMBER_FRAME);

      // Remove the IFRAME - we can't reuse it one its been moved inline
      if (iframeEl) {
        iframeEl.remove();
      }
    }

    // Hide the iframe
    if (this.iframeEl) {
      this.iframeEl.classList.add(EMBER_FRAME_HIDE_CLASS);
    }

    // Cancel any pending http request to check Ember UI availability
    if (this.emberCheck) {
      this.emberCheck.cancel('User left page');
    }

    // Set up a timer to remove the IFrame after a period of inactivity
    inactiveRemoveTimer = setTimeout(() => {
      const iframeEl = document.getElementById(EMBER_FRAME);

      if (iframeEl !== null) {
        iframeEl.remove();
      }
    }, INACTIVITY_CHECK_TIMEOUT);
  },

  methods: {
    addBannerClasses(elm, prefix) {
      if (!elm) {
        return;
      }

      elm.classList.remove(`${ prefix }-top-banner`);
      elm.classList.remove(`${ prefix }-one-banner`);
      elm.classList.remove(`${ prefix }-two-banners`);

      if (this.showHeaderBanner) {
        elm.classList.add(`${ prefix }-top-banner`);
        if (this.showFooterBanner) {
          elm.classList.add(`${ prefix }-two-banners`);
        }
      } else if (this.showFooterBanner) {
        elm.classList.add(`${ prefix }-one-banner`);
      }
    },

    async initFrame() {
      const bannerSetting = await this.$store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.BANNERS);

      try {
        const parsed = JSON.parse(bannerSetting.value);

        this.showHeaderBanner = parsed.showHeader === 'true';
        this.showFooterBanner = parsed.showFooter === 'true';
      } catch {}

      this.loaded = true;
      this.loadRequired = false;

      // Get the existing iframe if it exists
      let iframeEl = document.getElementById(EMBER_FRAME);

      // If the iframe already exists, check if it is ready for us to reuse
      // by navigating within the app that is already loaded
      if (iframeEl !== null) {
        const ready = iframeEl.getAttribute('data-ready') !== 'false';
        const lastDidLoad = iframeEl.getAttribute('data-loaded') !== 'false';
        const doNotReuse = !!this.inline && !this.forceReuse;
        // Was not inline but now is - can't reuse
        const inlineChanged = !!this.inline && (iframeEl.parentElement === document.body);

        if (!ready || doNotReuse || !lastDidLoad || inlineChanged) {
          iframeEl.remove();
          iframeEl = null;
        }
      }

      if (iframeEl === null && process.env.dev) {
        // Fetch a page to check that the Ember UI is available
        try {
          this.error = false;
          this.loaded = false;
          this.emberCheck = this.$axios.CancelToken.source();

          // Make a head requst to a known asset of the Ember UI
          const pageUrl = `${ window.location.origin }/assets/images/logos/rke.svg`;
          const response = await this.$axios.head(pageUrl, {
            timeout:     PAGE_CHECK_TIMEOUT,
            cancelToken: this.emberCheck.token,
          });

          if (response.status !== 200) {
            this.loaded = true;
            this.error = true;
          }
        } catch (e) {
          if (!this.$axios.isCancel(e)) {
            this.loaded = true;
            this.error = true;
          }
        }
      }

      if (this.error) {
        return;
      }

      if (iframeEl === null) {
        iframeEl = document.createElement('iframe');
        iframeEl.setAttribute('id', EMBER_FRAME);
        iframeEl.classList.add(EMBER_FRAME_HIDE_CLASS);

        if (this.inline) {
          const frameParent = document.getElementById(this.inline);

          frameParent.appendChild(iframeEl);
        } else {
          document.body.append(iframeEl);
        }
        iframeEl.setAttribute('src', this.src);
      } else {
        // Reset scroll position to top
        if (iframeEl.contentWindow?.scrollTo) {
          iframeEl.contentWindow.scrollTo(0, 0);
        }

        // Post a message to navigate within the existing app
        iframeEl.contentWindow.postMessage({
          action: 'navigate',
          name:   this.src
        });

        // Ensure iframe gets the latest theme if it has changed
        this.notifyTheme(this.theme);

        const currentUrl = iframeEl.contentWindow.location.pathname;
        const src = this.trimURL(this.src);

        if (src !== currentUrl) {
          iframeEl.classList.add(EMBER_FRAME_HIDE_CLASS);
        } else {
          iframeEl.classList.remove(EMBER_FRAME_HIDE_CLASS);
        }
      }

      this.iframeEl = iframeEl;

      if (!this.inline) {
        iframeEl.classList.add('ember-iframe');
        iframeEl.classList.remove('ember-iframe-inline');
        this.addBannerClasses(this.$refs.emberPage, 'fixed');
        this.addBannerClasses(iframeEl, 'ember-iframe');

        // If the window manager is open, sync the size
        if (this.open) {
          this.syncSize();
        }
      } else {
        iframeEl.classList.remove('ember-iframe');
        iframeEl.classList.add('ember-iframe-inline');
        iframeEl.height = 0;
        this.syncSize();
      }
    },

    syncSize() {
      if (this.heightSync) {
        clearTimeout(this.heightSync);
      }

      this.heightSync = setTimeout(() => {
        this.dosyncSize();
        this.syncSize();
      }, 500);
    },

    dosyncSize() {
      if (this.inline) {
        const iframeEl = document.getElementById(EMBER_FRAME);
        const doc = iframeEl.contentWindow.document;
        const app = doc.getElementById('application');
        const h = app?.offsetHeight;

        if (h && this.frameHeight !== h) {
          this.frameHeight = h;
          iframeEl.height = h;
        }

        const frameParent = document.getElementById(this.inline);
        const w = frameParent.offsetWidth;

        if (w && this.frameWidth !== w) {
          this.frameWidth = w;
          iframeEl.width = w;
        }
      } else {
        // Ensure the height takes into count the window manger height
        const wm = document.getElementById(WINDOW_MANAGER);

        if (wm) {
          const wmh = wm.offsetHeight;

          if (wmh !== this.wmHeight) {
            // Adjust the bottom
            const iframeEl = document.getElementById(EMBER_FRAME);

            iframeEl.style.height = `calc(100vh - var(--header-height) - ${ wmh }px)`;
            this.wmHeight = wmh;
          }
        }
      }
    },

    notifyTheme(theme) {
      const iframeEl = document.getElementById(EMBER_FRAME);

      if (iframeEl) {
        const emberTheme = theme === 'light' ? 'ui-light' : 'ui-dark';

        // Ensure the embedded UI uses the correct theme
        iframeEl.contentWindow.postMessage({
          action: 'set-theme',
          name:   emberTheme
        });
      }
    },

    trimURL(url) {
      if (url && url.endsWith('/')) {
        url = url.substr(0, url.length - 1);
      }

      return url;
    },

    // We use PostMessage between the Embedded Ember UI and the Dashboard UI
    receiveMessage(event) {
      const msg = event.data;

      if (msg.action === 'navigate') {
        this.$router.replace({
          name:   'c-cluster-explorer',
          params: { cluster: msg.cluster }
        });
      } else if (msg.action === 'before-navigation') {
        this.$emit('before-nav', msg.target);

        // Ember willTransition event
        if (INTERCEPTS[msg.target]) {
          const dest = INTERCEPTS[msg.target];

          if (this.isCurrentRoute(dest)) {
            this.setLoaded(true);

            return;
          }

          this.setLoaded(false);
          this.$router.replace(this.fillRoute(dest));
        }
      } else if (msg.action === 'loading') {
        this.setLoaded(!msg.state);
        this.updateFrameVisibility();
      } else if (msg.action === 'ready') {
        // Echo back a ping
        this.iframeEl.contentWindow.postMessage({ action: 'echo-back' });
        this.iframeEl.setAttribute('data-ready', true);

        const doc = this.iframeEl.contentWindow?.document?.body;

        if (this.inline) {
          doc.classList.add('embedded-no-overflow');
        } else {
          doc.classList.remove('embedded-no-overflow');
        }
      } else if (msg.action === 'need-to-load') {
        this.loadRequired = true;
      } else if (msg.action === 'did-transition') {
        if (!this.loadRequired) {
          this.setLoaded(true);
          this.updateFrameVisibility();
          this.dosyncSize();
        }
      } else if (msg.action === 'dashboard') {
        this.iframeEl.setAttribute('data-ready', false);
        this.$router.replace(msg.page);
      } else if (msg.action === 'reload') {
        this.loaded = false;
        this.iframeEl.remove();
        this.initFrame();
      }
    },

    setLoaded(loaded) {
      this.loaded = loaded;
      if (this.iframeEl) {
        this.iframeEl.setAttribute('data-loaded', loaded);
      }
    },

    updateFrameVisibility() {
      if (this.loaded) {
        if (this.iframeEl) {
          this.iframeEl.classList.remove(EMBER_FRAME_HIDE_CLASS);

          // Notify the embedded UI of the primary and primary text colors
          const primary = window.getComputedStyle(document.body).getPropertyValue('--primary');
          const primaryText = window.getComputedStyle(document.body).getPropertyValue('--primary-text');

          this.iframeEl.contentWindow.postMessage({
            action: 'colors',
            primary,
            primaryText,
          });
        }
      }
    },

    fillRoute(route) {
      if (typeof route === 'object') {
        // Fill in standard params
        if (route.params) {
          if ('cluster' in route.params) {
            route.params.cluster = this.clusterId;
          }
          if ('product' in route.params) {
            route.params.product = this.productId;
          }
        }
      }

      return route;
    },

    isCurrentRoute(route) {
      const current = this.$route;

      if (current.name === route.name) {
        let same = true;

        Object.keys(current.params).forEach((p) => {
          if (route.params[p] !== current.params[p]) {
            same = false;
          }
        });

        return same;
      }

      return false;
    }
  }
};
</script>

<template>
  <div ref="emberPage" class="ember-page">
    <Loading v-if="!inline" :loading="!loaded" mode="content" :no-delay="true" />
    <div v-if="inline && !loaded" class="inline-loading" v-html="t('generic.loading', {}, true)" />
    <div v-if="error" class="ember-page-error">
      <div>{{ t('embedding.unavailable') }}</div>
      <button class="btn role-primary" @click="initFrame()">
        {{ t('embedding.retry') }}
      </button>
    </div>
  </div>
</template>

<style lang="scss" scoped>
  $banner-height: 2em;

  .fixed-top-banner {
    top: calc(#{$banner-height} + var(--header-height));
  }

  .fixed-one-banner {
    height: calc(100vh - var(--header-height) - #{$banner-height});
  }

  .fixed-two-banners {
    height: calc(100vh - var(--header-height) - #{$banner-height} - #{$banner-height});
  }

  .ember-page {
    display: flex;
    height: 100%;
    padding: 0;
  }

  .frame {
    flex: 1;
    visibility: hidden;
  }
  .frame.pop {
    margin: -20px;
  }

  .loading {
    visibility: visible;
  }
  .ember-page-error {
    display: flex;
    align-items: center;
    flex: 1;
    flex-direction: column;
    justify-content: center;
    > div {
      font-size: 20px;
      padding-bottom: 20px;
    }
  }
  .inline-loading {
    border: 1px solid var(--border);
    border-radius: 5px;
    padding: 10px;
    text-align: center;
    width: 100%;
  }
</style>
<style lang="scss">
  $banner-height: 2em;

  .ember-iframe {
    border: 0;
    left: var(--nav-width);
    height: calc(100vh - var(--header-height));
    position: absolute;
    top: var(--header-height);
    width: calc(100vw - var(--nav-width));
    visibility: show;
  }

  .ember-iframe-top-banner {
    top: calc(#{$banner-height} + var(--header-height));
  }

  .ember-iframe-one-banner {
    height: calc(100vh - var(--header-height) - #{$banner-height});
  }

  .ember-iframe-two-banners {
    height: calc(100vh - var(--header-height) - #{$banner-height} - #{$banner-height});
  }

  .ember-iframe-inline {
    border: 0;
    overflow: hidden;
  }

  .ember-iframe-hidden {
    visibility: hidden;
  }
</style>
